<?php

/***
 * Class CW_SmtpClass retrieves is a php implementation
 * of the SMTP protocol. This object creates an interface that
 * allows an application to send email messages using the SMTP
 * protocol.
 ***/
class CW_SmtpClass {

	/***
	 * Username to log into an account.
	 * @type String
	 ***/
	var $_user_name;


	/***
	 * User password to log into an account.
	 * @type String
	 ***/
	var $_user_password;


	/***
	 * Port to connect to SMTP server.
	 * @type Integer
	 ***/
	var $_server_port;


	/***
	 * Location of host that provides an SMTP service.
	 * @type String
	 ***/
	var $_server_host;


	/***
	 * Location of host to hail.
	 * @type String
	 ***/
	var $_server_helo;


	/***
	 * Cancels a connection request after 'x' seconds.
	 * @type Integer
	 ***/
	var $_server_timeout;


	/***
	 * The server's response to an issued command.
	 * @type String
	 ***/
	var $_server_response;


	/***
	 * The server response identifier for commands.
	 * @type Integer
	 ***/
	var $_server_response_code;


	/***
	 * Carriage return string.
	 * @type String
	 ***/
	var $_crlf_string;


	/***
	 * Resource that holds the current connection id.
	 * @type Resource
	 ***/
	var $_connection_id;


	// ! Constructor

	/***
	 * Insantiates object.
	 * @param none
	 ***/
	function SmtpMailHandlingClass()
	{
		$this->_user_name            = '';
		$this->_user_password        = '';
		$this->_server_port          = 25;
		$this->_server_host          = 'localhost';
		$this->_server_helo          = 'localhost.localdomain';
		$this->_server_timeout       = 30;
		$this->_server_response      = '';
		$this->_server_response_code = '';
		$this->_crlf_string          = "\r\n";
		$this->_connection_id        = null;
	}


   // ! Accessor

   /**
	* Checks if there is a currently active connection
	* to an SMTP-enabled host server.
	*
	* @param none
	* @return Bool
	*/
	function checkIfConnected()
	{
		return is_resource ( $this->_connection_id );
	}


   // ! Executor

   /**
	* Issues a command to the server and fetches any
	* command responses.
	*
	* @param $command The command to send.
	* @return Integer
	*/
	function sendCommandToServer ( $command )
	{
		fputs ( $this->_connection_id, "{$command}{$this->_crlf_string}" );

		return $this->getResponseFromServer();
	}


   // ! Accessor

   /**
	* Fetches responses and response codes from
	* a server after a command has  been issued.
	*
	* @param none
	* @return Integer
	*/
	function getResponseFromServer()
	{
		$response = '';

		while ( $data = fgets ( $this->_connection_id, 515 ) )
		{
			$response = $data;

			if ( substr ( $data, 3, 1 ) == ' ' )
			{
				break;
			}
		}

		$this->_server_response      = $response;
		$this->_server_response_code = substr ( $response, 0, 3 );

		if ( false == trim ( $this->_server_response_code ) )
		{
			$this->_server_response_code = 250;
		}

		return $this->_server_response_code;
	}


   // ! Accessor

   /**
	* Attempts to open a connection to a specified smtp
	* service enabled server.
	*
	* @param $server_host    Location of SMTP service provider
	* @param $server_port    Port number assigned to SMTP service
	* @param $server_timeout Give up trying to connect after this amount of seconds.
	* @return String
	*/
	function openConnectionToServer ( $server_host = 'localhost', $server_port = 25, $server_timeout = 25 )
	{
		$this->_server_host    = $server_host;
		$this->_server_port    = $server_port;
		$this->_server_timeout = $server_timeout;

		$this->_connection_id = fsockopen ( $this->_server_host,
											$this->_server_port,
											$errno,
											$errstr,
											$this->_server_timeout );

		if ( substr ( PHP_OS, 0, 3 ) != 'WIN' )
		{
			socket_set_timeout ( $this->_connection_id, 1, 0 );
		}

		if ( false == $this->checkIfConnected() )
		{
			return $this->_exitWithAnError ( 'A connection could not be established.' );
		}

		$this->getResponseFromServer();

		return $this->_server_response;
	}


   // ! Executor

   /**
	* ThIS command is used to identify the SMTP client to 
	* the SMTP server.
	*
	* @param $hello The location of the SMTP client.
	* @return Integer
	*/
	function cmdHelo ( $hello )
	{
		return $this->_formatAndSendCommand ( "HELO {$hello}" );
	}


   // ! Executor

   /**
	* Allows the system to log into a user's account.
	*
	* @param $username Name for user's account
	* @param $password Password for user's account
	* @return Integer
	*/
	function cmdAuthLogin ( $username, $password )
	{
		$this->_formatAndSendCommand ( 'AUTH LOGIN', array ( 334 ) );

		$this->_user_name     = $username;
		$this->_user_password = $password;

		$this->_formatAndSendCommand ( base64_encode ( $this->_user_name ),     array ( 334 ) );
		$this->_formatAndSendCommand ( base64_encode ( $this->_user_password ), array ( 235 ) );

		return true;
	}


   // ! Executor

   /**
	* This command specifies that the receiver MUST send an
	* OK reply, and then close the transmission channel.
	*
	* @param none
	* @return Integer
	*/
	function cmdQuit()
	{
		$this->_formatAndSendCommand ( 'QUIT', array ( 221 ) );

		fclose ( $this->_connection_id );

		$this->_connection_id = null;

		return $this->_server_response_code;
	}


   // ! Executor

   /**
	* The receiver normally sends a 354 response to DATA, and then treats
	* the lines (strings ending in <CRLF> sequences ) following the command 
	* as mail data from the sender.
	*
	* @param $data Message data to send; usually headers and the message.
	* @return Integer
	*/
	function cmdData ( $data )
	{
		$this->_formatAndSendCommand ( 'DATA', array ( 354 ) );

		return $this->sendCommandToServer ( $data . $this->_crlf_string . $this->_crlf_string . $this->_crlf_string . '.' );
	}


   // ! Executor

   /**
	* This command asks the receiver to confirm that the argument
	* identifies a mailing list, and if so, to return the membership of
	* that list. 
	*
	* @param none
	* @return Integer
	*/
	function cmdExpand ( $name )
	{
		$this->_formatAndSendCommand ( "EXPN {$name}" );

		$list = array();

		foreach ( explode ( $this->_crlf_string, $this->_server_response ) as $entry )
		{
			$list[] = substr ( $entry, 4 );
		}

		return $list ? $list : false;
	}


   // ! Executor

   /**
	* This command causes the server to send helpful information to the
	* client.  The command MAY take an argument (e.g., any command name)
	* and return more specific information as a response.
	*
	* @param $keyword Search term to find help for.
	* @return Integer
	*/
	function cmdHelp ( $keyword )
	{
		return $this->_formatAndSendCommand ( "HELP {$keyword}", array ( 211, 214 ), 'response' );
	}


   // ! Executor

   /**
	* This command is used to initiate a mail transaction in which the mail
	* data is delivered to an SMTP server which may, in turn, deliver it to
	* one or more mailboxes or pass it on to another system (possibly using
	* SMTP).  The argument field contains a reverse-path and may contain
	* optional parameters.  In general, the MAIL command may be sent only
	* when no mail transaction is in progress
	*
	* @param $from Sender's email address.
	* @return Integer
	*/
	function cmdMail ( $from )
	{
		return $this->_formatAndSendCommand ( "MAIL FROM: <{$from}>" );
	}


   // ! Executor

   /**
	* This command does not affect any parameters or previously entered
	* commands.  It specifies no action other than that the receiver send
	* an OK reply.
	*
	* @param none
	* @return Integer
	*/
	function cmdNoop()
	{
		return $this->_formatAndSendCommand ( 'NOOP' );
	}


   // ! Executor

   /**
	* The first or only argument to this command includes a forward-path
	* (normally a mailbox and domain, always surrounded by "<" and ">"
	* brackets) identifying one recipient.  If accepted, the SMTP server
	* returns a 250 OK reply and stores the forward-path.
	*
	* @param $recipient Email address of message recipient.
	* @return Integer
	*/
	function cmdRecipient ( $recipient )
	{
		return $this->_formatAndSendCommand ( "RCPT TO: <{$recipient}>", array ( 250, 251 ) );
	}


   // ! Executor

   /**
	* This command specifies that the current mail transaction will be
	* aborted.
	*
	* @param none
	* @return Integer
	*/
	function cmdReset()
	{
		return $this->_formatAndSendCommand ( 'RSET' );
	}

   // ! Executor

   /**
	* Verifies that the user / account name is recognized by the server.
	*
	* @param $name Name of account to verify.
	* @return Mixed
	*/
	function cmdVerify ( $name )
	{
		return $this->_formatAndSendCommand ( "VRFY {$name}", array ( 251, 250 ), 'response' );
	}


   // ! Executor

   /**
	* This is a utility method that takes care of some of 
	* redundant tasks throughout this class. It checks for 
	* a live connection before sending the command and also
	* does response code checking and will stop on an error
	* if on is found.
	*
	* @param $command    Command string to send to server.
	* @param $stop_codes SMTP response codes that validate the command.
	* @param $return Denotes what type of content to return.
	* @return Integer
	*/
	function _formatAndSendCommand ( $command, $stop_codes = array ( 250 ), $return = 'code' )
	{
		if ( false == $this->checkIfConnected() )
		{
			return $this->_exitWithAnError ( "No connection found for <code>{$command}</code> command." );
		}

		$this->sendCommandToServer ( $command );

		$passed = false;

		foreach ( $stop_codes as $code )
		{
			if ( $this->_server_response_code == $code )
			{
				$passed = true;
			}
		}

		if ( false == $passed )
		{
			return $this->_exitWithAnError ( "Command <code>{$command}</code> was not accepted by the server." );
		}

		if ( $return == 'code' )
		{
			return $this->_server_response_code;
		}

		return $this->_server_response;
	}


   // ! Executor

   /**
	* Creates a formatted error screen for debuggin purposes.
	*
	* @param $message Optional message to include with the error.
	* @return Void
	*/
	function _exitWithAnError ( $message = '' )
	{
		$die  = '<h2>Error Detected:</h2>';

		if ( $message )
		{
			$die  .= "<p>{$message}</p>";
		}

		$die .= "<pre>Code: {$this->_server_response_code}</pre>";
		$die .= "<pre>Response:\n\n{$this->_server_response}</pre>";

		die ( $die );
	}
}

?>